package weixin

import (
	"context"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"gitee.com/filters/utils/errv"
	"gitee.com/filters/utils/httpv"
	"github.com/tidwall/gjson"
	"sort"
	"time"
)

type Wxer struct {
	appid  string
	secret string
	token  string
}

var (
	NotFoundCode = errv.New(10001, "code为空")
	RequestOpenidFail = errv.New(10002, "openid请求失败")
	NotFoundAccessToken = errv.New(10003, "没有找到access_token")
)


type WxInfo struct {
	Openid     string `json:"openid"`
	SessionKey string `json:"session_key"`
	UnionId    string `json:"union_id"`
	Errcode    int32  `json:"errcode"`
	Errmsg     string `json:"errmsg"`
}

type Option func(wxer *Wxer)

func WithSecret(secret string) Option {
	return func(wxer *Wxer) {
		wxer.secret = secret
	}
}

func WithAppid(appid string) Option {
	return func(wxer *Wxer) {
		wxer.appid = appid
	}
}

func WithToken(token string) Option {
	return func(wxer *Wxer) {
		wxer.token = token
	}
}

//NewWx 初始化
func NewWx(opt ...Option) *Wxer {
	wx := &Wxer{}
	for _, option := range opt {
		option(wx)
	}
	return wx
}

//AccessToken ...
func (w *Wxer) AccessToken() (string,error) {
	ctx := context.Background()
	var token string
	ctx, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Second))
	defer cancel()
	url := fmt.Sprintf(AccessTokenUrl, WxBaseUrl, w.appid, w.secret)
	result, err := httpv.HttpGet(ctx, url, map[string]string{})
	if err != nil {
		return token, err
	}
	access := gjson.GetBytes(result,"access_token")
	if !access.Exists() {
		return token, NotFoundAccessToken
	}
	return access.String(), nil
}

// Verify 服务器验证
func (w *Wxer) Verify(ctx context.Context,sign,stamp,nonce string) bool {
	//将token、timestamp、nonce三个参数进行字典序排序
	var tempArray = []string{w.token, stamp, nonce}
	sort.Strings(tempArray)
	//将三个参数字符串拼接成一个字符串进行sha1加密
	var sha1String string
	for _, v := range tempArray {
		sha1String += v
	}
	h := sha1.New()
	h.Write([]byte(sha1String))
	sha1String = hex.EncodeToString(h.Sum([]byte("")))
	if sha1String == sign {
		return true
	}
	return false
}

// GetOpenid 根据code获取openid
func (w *Wxer) GetOpenid(code string) (WxInfo, error) {
	var info WxInfo
	if code == "" {
		return info, NotFoundCode
	}
	ctx := context.Background()
	ctx, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Second))
	defer cancel()
	url := fmt.Sprintf(Code2sessionURL, WxBaseUrl, w.appid, w.secret, code)
	result, err := httpv.HttpGet(ctx, url, map[string]string{})
	if err != nil {
		return info, err
	}

	err = json.Unmarshal(result, &info)
	if err != nil {
		return info, err
	}

	if info.Errcode != 0 {
		return info,RequestOpenidFail.WithDetail(info.Errmsg)
	}
	return info, nil
}
